/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       scenenode.cpp
 * Author:     karooolek
 * Created on: 2009-03-10
 *
 **********************************************************************************************************************/

#include "scenenode.h"
#include "scene.h"

namespace mGameEngine
{
namespace Game
{

using namespace Graphics;
using namespace Audio;

SceneNode::SceneNode(const String &name) :
    _name(name), _flags(),
    _scene(NULL), _parent(NULL), _nodes(),
    _transformation(1.0f),
    _camera(NULL), _lights(), _drawables(),
    _microphone(NULL), _soundSources(),
    _actor(NULL)
{
}

SceneNode::~SceneNode()
{
    // detach from scene
    if(_scene && !_parent)
    {
        _scene->setRootSceneNode(NULL);
    }

//     // delete sub-nodes
//     while(!_nodes.empty())
//     {
//         delete _nodes.back();
//         _nodes.pop_back();
//     }

//     // delete drawables?
//     while(!_drawables.empty())
//     {
//         delete _drawables.back();
//         _drawables.pop_back();
//     }
// 
//     // delete sounds?
//     while(!_soundSources.empty())
//     {
//         delete _soundSources.back();
//         _soundSources.pop_back();
//     }
// 
//     // FIX delete actor?
//     if(_actor)
//     {
//         delete _actor;
//     }
}

void SceneNode::_setScene(Scene *scene)
{
    _scene = scene;
    for(List<SceneNode *>::Iterator i = _nodes.begin(); i != _nodes.end(); ++i)
    {
        SceneNode *node = *i;
        node->_setScene(scene);
    }
}

Scene *SceneNode::getScene() const
{
    return _scene == NULL && _parent != NULL ? _scene = _parent->getScene() : _scene;
}

SceneNode *SceneNode::getRootSceneNode() const
{
    SceneNode *n;
    for(n = const_cast<SceneNode *>(this); n->_parent; n = n->_parent);
    return n;
}

void SceneNode::addSceneNode(SceneNode *node)
{
    // no NULL nodes
    if(!node)
    {
        return;
    }

    // avoid creating cycles in tree
    for(SceneNode *n = this; n; n = n->_parent)
    {
        if(n == node)
        {
            return;
        }
    }

    _nodes.push_back(node);
    _nodes.unique();

    // set scene and parent
    node->_setScene(_scene);
    node->_parent = this;
}

void SceneNode::removeSceneNode(SceneNode *node)
{
    // no NULL nodes
    if(!node)
    {
        return;
    }

    _nodes.remove(node);
}

bool SceneNode::hasSceneNode(const String &name) const
{
    for(List<SceneNode *>::ConstIterator i = _nodes.begin(); i != _nodes.end(); ++i)
    {
        SceneNode *node = *i;

        if(node && node->_name == name)
        {
            return true;
        }
    }

    return false;
}

SceneNode *SceneNode::getSceneNode(const String &name) const
{
    for(List<SceneNode *>::ConstIterator i = _nodes.begin(); i != _nodes.end(); ++i)
    {
        SceneNode *node = *i;

        if(node && node->_name == name)
        {
            return node;
        }
    }

    return NULL;
}

Vector3 SceneNode::getPosition(const SceneNode *relative) const
{
    // self-relative
    if(relative == this)
    {
        return Vector3();
    }
    // relative to parent node
    else if(!relative || relative == _parent)
    {
        return Vector3(_transformation.m[3]);
    }
    // distant relation
    else
    {
        return Vector3(getTransformation(relative).m[3]);
    }
}

void SceneNode::setPosition(const Vector3 &position, const SceneNode *relative)
{
    // self-relative
    if(relative == this)
    {
        _transformation *= Matrix4x4(Quaternion(), position);
    }
    // relative to parent node
    else if(!relative || relative == _parent)
    {
        _transformation.m[3][0] = position.x;
        _transformation.m[3][1] = position.y;
        _transformation.m[3][2] = position.z;
    }
    // distant relation
    else
    {
        Matrix4x4 t = getTransformation(relative);
        t.m[3][0] = position.x;
        t.m[3][1] = position.y;
        t.m[3][2] = position.z;
        setTransformation(t, relative);
    }
}

void SceneNode::move(const Vector3 &move, const SceneNode *relative)
{
    // self relative
    if (relative == this)
    {
        _transformation *= Matrix4x4(Quaternion(), move);
    }
    // relative to parent node
    else if(!relative || relative == _parent)
    {
        _transformation.m[3][0] += move.x;
        _transformation.m[3][1] += move.y;
        _transformation.m[3][2] += move.z;
    }
    // TEST distant relation
    else
    {
        Matrix4x4 t = getTransformation(relative);
        t.m[3][0] += move.x;
        t.m[3][1] += move.y;
        t.m[3][2] += move.z;
        setTransformation(t, relative);
    }
}

Quaternion SceneNode::getOrientation(const SceneNode *relative) const
{
    // self-relative
    if(relative == this)
    {
        return Quaternion();
    }
    // relative to parent node
    else if(!relative || relative == _parent)
    {
        return Quaternion(_transformation);
    }
    // distant relation
    else
    {
        return Quaternion(getTransformation(relative));
    }
}

void SceneNode::setOrientation(const Quaternion &orientation, const SceneNode *relative)
{
    // self-relative
    if(relative == this)
    {
        _transformation *= Matrix4x4(orientation, Vector3());
    }
    // relative to parent node
    else if(!relative || relative == _parent)
    {
        _transformation = Matrix3x3(orientation);
    }
    // distant relation
    else
    {
        Matrix4x4 t = getTransformation(relative);
        t = Matrix3x3(orientation);
        setTransformation(t, relative);
    }
}

void SceneNode::rotate(const Quaternion &rotate, const SceneNode *relative)
{
    // self-relative
    if(relative == this)
    {
        _transformation *= Matrix4x4(rotate, Vector3());
    }
    // relative to parent node
    else if(!relative || relative == _parent)
    {
        _transformation *= Matrix4x4(rotate, Vector3());
    }
    // TODO TEST distant relation
    else
    {
        Matrix4x4 t = getTransformation(relative);
        t *= Matrix4x4(rotate, Vector3());
        setTransformation(t, relative);
    }
}

Matrix4x4 SceneNode::getTransformation(const SceneNode *relative) const
{
    // relative to parent
    if(!relative || relative == _parent)
    {
        return _transformation;
    }
    // self-relative
    else if(relative == this)
    {
        return Matrix4x4(1.0f);
    }

    // distant relation
    Matrix4x4 A(_transformation);
    for(const SceneNode *n = _parent; n; n = n->_parent)
    {
        // relative found on path to root
        if(n == relative)
        {
            return A;
        }

        A = n->_transformation * A;
    }

    Matrix4x4 B(relative->_transformation);
    for(const SceneNode *n = relative->_parent; n; n = n->_parent)
    {
        B = n->_transformation * B;
    }

    B.invert();
    B *= A;

    return B;
}

void SceneNode::setTransformation(const Matrix4x4 &t, const SceneNode *relative)
{
    // relative to parent
    if(!relative || relative == _parent)
    {
        _transformation = t;
    }
    // self-relative
    else if(relative == this)
    {
        _transformation *= t;
    }
    // distant relation
    else
    {
        _transformation = relative->getTransformation(_parent) * t;
    }
}

void SceneNode::addLight(Light *light)
{
    if(light)
    {
        _lights.push_back(light);
        _lights.unique();
    }
}

void SceneNode::removeLight(Light *light)
{
    if(light)
    {
        _lights.remove(light);
    }
}

void SceneNode::addDrawable(Drawable *drawable)
{
    if(drawable)
    {
        _drawables.push_back(drawable);
        _drawables.unique();
    }
}

void SceneNode::removeDrawable(Drawable *drawable)
{
    if(drawable)
    {
        _drawables.remove(drawable);
    }
}

void SceneNode::addSoundSource(SoundSource *soundSource)
{
    if(soundSource)
    {
        _soundSources.push_back(soundSource);
        _soundSources.unique();
    }
}

void SceneNode::removeSoundSource(SoundSource *soundSource)
{
    if(soundSource)
    {
        _soundSources.remove(soundSource);
    }
}

void SceneNode::idle()
{
}

void SceneNode::update(float dt)
{
}

}
}

